﻿using System.Numerics;
using System.Text.Json.Serialization;

namespace LightCAD.Core
{

    public struct Matrix3d
    {
        [JsonInclude]
        public double m11;
        [JsonInclude]
        public double m12;
        [JsonInclude]
        public double m13;
        [JsonInclude]
        public double m21;
        [JsonInclude]
        public double m22;
        [JsonInclude]
        public double m23;
        [JsonInclude]
        public double m31;
        [JsonInclude]
        public double m32;
        [JsonInclude]
        public double m33;


        public Matrix3d(
            double m11 = 0.0, double m12 = 0.0, double m13 = 0.0,
            double m21 = 0.0, double m22 = 0.0, double m23 = 0.0,
            double m31 = 0.0, double m32 = 0.0, double m33 = 0.0)
        {
            this.m11 = m11;
            this.m12 = m12;
            this.m13 = m13;
            this.m21 = m21;
            this.m22 = m22;
            this.m23 = m23;
            this.m31 = m31;
            this.m32 = m32;
            this.m33 = m33;
        }

        public void Set(
            double m11 = 0.0, double m12 = 0.0, double m13 = 0.0,
            double m21 = 0.0, double m22 = 0.0, double m23 = 0.0,
            double m31 = 0.0, double m32 = 0.0, double m33 = 0.0)
        {
            this.m11 = m11;
            this.m12 = m12;
            this.m13 = m13;
            this.m21 = m21;
            this.m22 = m22;
            this.m23 = m23;
            this.m31 = m31;
            this.m32 = m32;
            this.m33 = m33;
        }

        public override string ToString()
        {
            string pattern = @"Matrix3(
{0}, {1}, {2},
{3}, {4}, {5},
{6}, {7}, {8}  )";
            return string.Format(pattern,
                m11, m12, m13,
                m21, m22, m23,
                m31, m32, m33);
        }

        public double Determinant()
        {
            return m11 * m22 * m33 +
                   m12 * m23 * m31 +
                   m13 * m21 * m32 -
                   m11 * m23 * m32 -
                   m12 * m21 * m33 -
                   m13 * m22 * m31;
        }

        public Matrix3d Inverse()
        {

            double d = this.Determinant();
            if (d == 0.0)
                return Matrix3d.Identity;

            Matrix3d tmp = new Matrix3d();
            tmp.m11 = (m22 * m33 - m23 * m32) / d;
            tmp.m12 = (m13 * m32 - m12 * m33) / d;
            tmp.m13 = (m12 * m23 - m13 * m22) / d;
            tmp.m21 = (m23 * m31 - m21 * m33) / d;
            tmp.m22 = (m11 * m33 - m13 * m31) / d;
            tmp.m23 = (m13 * m21 - m11 * m23) / d;
            tmp.m31 = (m21 * m32 - m22 * m31) / d;
            tmp.m32 = (m12 * m31 - m11 * m32) / d;
            tmp.m33 = (m11 * m22 - m12 * m21) / d;
            return tmp;
        }

        public Matrix3d Transpose()
        {
            return new Matrix3d(
                m11, m21, m31,
                m12, m22, m32,
                m13, m23, m33);
        }

        public Vector2d MultiplyPoint(Vector2d v)
        {
            return new Vector2d(
                m11 * v.X + m12 * v.Y + m13,
                m21 * v.X + m22 * v.Y + m23);
        }
        public Vector2d MultiplyPoint(double x, double y)
        {
            return new Vector2d(
                m11 * x + m12 * y + m13,
                m21 * x + m22 * y + m23);
        }
        public Vector2d MultiplyVector(Vector2d v)
        {
            return new Vector2d(
                m11 * v.X + m12 * v.Y,
                m21 * v.X + m22 * v.Y);
        }
        public Vector2d MultiplyVector(double x, double y)
        {
            return new Vector2d(
                m11 * x + m12 * y,
                m21 * x + m22 * y);
        }
       
        public Vector2d GetTranslateVector()
        {
            return new Vector2d(m13, m23);
        }
        public Vector2d GetScaleVector()
        {
            //https://math.stackexchange.com/questions/13150/extracting-rotation-scale-values-from-2d-transformation-matrix/13165#13165
            return new Vector2d(Math.Sign(m11)*Math.Sqrt(m11*m11+m12*m12), Math.Sign(m21) * Math.Sqrt(m21 * m21 + m22 * m22));
        }
        public double GetAngle()
        {
            return Math.Atan2(-m12, m11);//Math.Atan2(m21, m22);
        }
        public override bool Equals(object obj)
        {
            if (!(obj is Matrix3d))
                return false;

            return Equals((Matrix3d)obj);
        }

        public bool Equals(Matrix3d a)
        {
            return Utils.IsEqual(this.m11, a.m11) && Utils.IsEqual(this.m12, a.m12) && Utils.IsEqual(this.m13, a.m13) &&
                Utils.IsEqual(this.m21, a.m21) && Utils.IsEqual(this.m22, a.m22) && Utils.IsEqual(this.m23, a.m23) &&
                Utils.IsEqual(this.m31, a.m31) && Utils.IsEqual(this.m32, a.m32) && Utils.IsEqual(this.m33, a.m33);
        }


        public static Matrix3d Identity { get; } = new Matrix3d(
                    1.0, 0.0, 0.0,
                    0.0, 1.0, 0.0,
                    0.0, 0.0, 1.0);

        public static Matrix3d Zero { get; } = new Matrix3d(
                    0.0, 0.0, 0.0,
                    0.0, 0.0, 0.0,
                    0.0, 0.0, 0.0);

        public static Matrix3d CoordSystem(Vector2d orgin, Vector2d xAxis, Vector2d yAxis)
        {
            return new Matrix3d(
                xAxis.X, yAxis.X, orgin.X,
                xAxis.Y, yAxis.Y, orgin.Y,
                0.0, 0.0, 1.0);
        }
        public static Matrix3d Translate(Vector2d v)
        {
            return new Matrix3d(
                1.0, 0.0, v.X,
                0.0, 1.0, v.Y,
                0.0, 0.0, 1.0);
        }
        public static Matrix3d Translate(double x, double y)
        {
            return new Matrix3d(
                1.0, 0.0, x,
                0.0, 1.0, y,
                0.0, 0.0, 1.0);
        }
        public static Matrix3d Rotate(double degreeAngle)
        {
            return RotateInRadian(Utils.DegreeToRadian(degreeAngle));
        }

        public static Matrix3d RotateInRadian(double angle)
        {
            double cos = Math.Cos(angle);
            double sin = Math.Sin(angle);
            return new Matrix3d(cos, -sin, 0.0, sin, cos, 0.0, 0.0, 0.0, 1.0);
        }

        public static Matrix3d Rotate(double degreeAngle, Vector2d center)
        {
            return RotateInRadian(Utils.DegreeToRadian(degreeAngle), center);
        }

        public static Matrix3d RotateInRadian(double angle, Vector2d center)
        {
            // Arbitrary rotation.
            var c = Math.Cos(angle);
            var s = Math.Sin(angle);

            var x = center.X * (1 - c) + center.Y * s;
            var y = center.Y * (1 - c) - center.X * s;

            return new Matrix3d(
                c, -s, x,
                s, c, y,
                0.0, 0.0, 1.0);
        }




        public static Matrix3d Scale(Vector2d v)
        {
            return new Matrix3d(
                v.X, 0.0, 0.0,
                0.0, v.Y, 0.0,
                0.0, 0.0, 1.0);
        }
        public static Matrix3d Scale(double scale)
        {
            return new Matrix3d(
                scale, 0.0, 0.0,
                0.0, scale, 0.0,
                0.0, 0.0, 1.0);
        }
        public static Matrix3d Scale(double scaleX, double scaleY)
        {
            return new Matrix3d(
                scaleX, 0.0, 0.0,
                0.0, scaleY, 0.0,
                0.0, 0.0, 1.0);
        }
        public static Matrix3d Scale(Vector2d scale, Vector2d center)
        {
            var z = center * (Vector2d.One - scale);
            return new Matrix3d(
                scale.X, 0.0, z.X,
                0.0, scale.Y, z.Y,
                0.0, 0.0, 1.0);
        }
        public static Matrix3d Mirror(Vector2d axisStart, Vector2d axisEnd)
        {

            double A = (axisStart.Y - axisEnd.Y);
            double B = (axisEnd.X - axisStart.X);
            double C = (axisStart.X * axisEnd.Y - axisEnd.X * axisStart.Y);
            double D = 1 / (Math.Pow(A, 2) + Math.Pow(B, 2));
            return new Matrix3d(
                D * (Math.Pow(B, 2) - Math.Pow(A, 2)), D * -2 * A * B, D * -2 * A * C,
               D * -2 * A * B, D * (Math.Pow(A, 2) - Math.Pow(B, 2)), D * -2 * B * C,
               0.0, 0.0, 1);
        }


        public static Matrix3d Scale(double scale, Vector2d center)
        {
            var z = center * (Vector2d.One - new Vector2d(scale, scale));
            return new Matrix3d(
                scale, 0.0, z.X,
                0.0, scale, z.Y,
                0.0, 0.0, 1.0);
        }
        public static Matrix3d Move(Vector2d startPoint, Vector2d EndPoint)
        {
            return new Matrix3d(
                1.0, 0.0, EndPoint.X - startPoint.X,
                0.0, 1.0, EndPoint.Y - startPoint.Y,
               0.0, 0.0, 1.0);
        }
        public static Matrix3d operator *(Matrix3d a, Matrix3d b)
        {
            return Multiply(a, b);
        }
        public static Vector2d operator *(Matrix3d m, Vector2d v)
        {
            return Multiply(m, v);
        }
        public static bool operator ==(Matrix3d a, Matrix3d b)
        {
            return a.Equals(b);
        }
        public static bool operator !=(Matrix3d a, Matrix3d b)
        {
            return !a.Equals(b);
        }
        public static Matrix3d Multiply(Matrix3d a, Matrix3d b)
        {
            Matrix3d M = new Matrix3d();
            M.m11 = a.m11 * b.m11 + a.m12 * b.m21 + a.m13 * b.m31;
            M.m12 = a.m11 * b.m12 + a.m12 * b.m22 + a.m13 * b.m32;
            M.m13 = a.m11 * b.m13 + a.m12 * b.m23 + a.m13 * b.m33;
            M.m21 = a.m21 * b.m11 + a.m22 * b.m21 + a.m23 * b.m31;
            M.m22 = a.m21 * b.m12 + a.m22 * b.m22 + a.m23 * b.m32;
            M.m23 = a.m21 * b.m13 + a.m22 * b.m23 + a.m23 * b.m33;
            M.m31 = a.m31 * b.m11 + a.m32 * b.m21 + a.m33 * b.m31;
            M.m32 = a.m31 * b.m12 + a.m32 * b.m22 + a.m33 * b.m32;
            M.m33 = a.m31 * b.m13 + a.m32 * b.m23 + a.m33 * b.m33;
            return M;
        }

        public static Vector2d Multiply(Matrix3d m, Vector2d v)
        {
            return new Vector2d(
                m.m11 * v.X + m.m12 * v.Y + m.m13,
                m.m21 * v.X + m.m22 * v.Y + m.m23);
        }

        internal double[] GetArray()
        {
            return new double[] { m11, m12, m13, m21, m22, m23, m31, m32, m33 };
        }
        internal void SetArray(double[] array)
        {
            m11 = array[0]; m12 = array[1]; m13 = array[2];
            m21 = array[3]; m22 = array[4]; m23 = array[5];
            m31 = array[6]; m32 = array[7]; m33 = array[8];
        }

        public Matrix3d Clone()
        {
            return new Matrix3d()
            {
                m11 = this.m11,
                m12 = this.m12,
                m13 = this.m13,
                m21 = this.m21,
                m22 = this.m22,
                m23 = this.m23,
                m31 = this.m31,
                m32 = this.m32,
                m33 = this.m33,
            };
        }
    }
}
